8.2.
Виртуални функции
Виртуалната функция е специална член функция викана
чрез указател към публичен базов клас или псевдоним на публичен базов клас; тя
се построява динамично по време на изпълнение. Извикваният образец се определя
чрез типа на класа на актуалния обект, адресиран от указателя или псевдонима. За
потребителя начинът на реализиране на една виртуална функция е
явен.
draw(), например, е виртуална функция с образци в ZooAnimal, Bear,
Panda, Cat и Leopard. Функция, викаща draw(), може да бъде дефинирана по следния
начин:
inline void draw( ZooAnimal& z )
{ z.draw(); }
Ако
един аргумент към не член образец на draw() адресира обект от класа Panda,
операторът z.draw() ще извика Panda::draw(). Един следващ аргумент, адресиращ
обект от класа Cat ще предизвика обръщение към Cat::draw(). Компилаторът решава
кои членове функции на класа да извика според на типа на класа на действителния
обект. Преди да разгледаме как се декларира и използва виртуална функция, нека
накратко видим защо искаме да използваме виртуални функции.
Динамичното
построяване е форма на капсулиране. Крайният екран на реализацията ZooAnimal
представя множество от животни, за които посетителят е искал информация. Този
екран, за удоволствие на децата, е направил от дисплейния терминал една
атракция.
За реализирането на множеството се поддържа свързан списък от
указатели към животни, за които посетителят ще бъде уведомяван. Когато се
натисне клавиша QUIT, главата на свързания списък от ZooAnimal се предава на
finalCollage(), която показва животните в подходящ размер и вид на
екрана.
Поддържането на свързания лист е просто, тъй като указател към
ZooAnimal може да адресира всеки публично извлечен клас. С динамичното
построяване не е сложно също да се определи извлечен клас, адресиран от указател
към ZooAnimal. finalCollage() може да бъде реализирана по следния
начин:
void finalCollage( ZooAnimal *pz )
{for ( ZooAnimal *p = pz; p
; p = p->next )
p->draw();}
В език, в който този проблем не е
разрешен по време на изпълнение, остава грижа на програмиста да определи
извлечен клас, адресиран от указател към ZooAnimal. Обикновено, това довежда до
идентифициране на члена на клас isA() и операторите if-else или switch, които
проверяват стойностите на isA(). Без динамично построение finalCollage може да
бъде реализирана по следния начин:
// nonobject-oriented
implementation
void finalCollage( ZooAnimal *pz )
{ for ( ZooAnimal *p =
pz; p ; p = p->next )
switch ( p->isA() )
{ case BEAR:
((Bear *)
p)->draw();break;
case PANDA:((Panda *) p)->draw();break;// ... every
other derived class} //
switch of isA}
Програмите, написани в този
стил, са основани върху детайли в реализацията на йерархията на извличането. Ако
тези детайли се променят, работата на програмата може да бъде нарушена и да се
наложи да се разшири кода на програмата.
След като пандите напуснат
зоопарка и си отидат в Китай, типът клас Panda може да се изтрие. Когато
пристигнат коали от Австралия, трябва да бъде прибавен тип клас Koala. За всяка
промяна в йерархията всеки оператор if-else и switch, който проверява типа на
класа, трябва да бъде променен. Програмният код се променя със всяка промяна на
йерархията.
Всеки оператор switch увеличава забележимо обема на
програмния код. Концептуално прости по своята същност действия се усложняват
поради условните тестове, необходими за проверка на типа клас на даден обект.
Програмите стават трудни за разчитане.
Потребителите на йерархията и
нейната реализация, желаещи да разширят йерархията или да използват приложенията
й, трябва да имат достъп до програмния код. Това прави поддържането на системата
доста по-трудно и за разпространителя, и за потребителя на системата.
Ако
проблемът е разрешен по време на изпълнение, това скрива от потребителя
детайлите по реализацията на йерархията на извличането. Условните проверки на
типа клас вече не са необходими. Това опростява потребителския код и го прави
нуждата от промени по-малка. Потребителският код, който вече не се променя със
всяка промяна на йерархията, е по-лесен за програмиране и поддържане.
На
свой ред това опростява разширяването на йерархията. Прибавянето на ново
извличане от ZooAnimal не изисква промяна в съществуващия код. Главната функция
draw() не се интересува от бъдещите извличания от ZooAnimal. Нейният код остава
функционален независимо от това в каква степен йерархията е била променена,
което означава, че реализацията с изключение на header-файловете може да бъде
разпространена в двоичен вид.
Опростява се също използването на
системата. Тъй като и реализацията на типовете класове, и реализацията на
йерархията на класовете са капсулирани, те могат да бъдат променяни с минимални
добавки в кода на клиента като остава непроменен публичният
интерфейс.
Дефиниция на виртуални функции
Една виртуална функция
се определя като се прибави в началото на декларацията на функцията ключовата
дума virtual. Само функции,членове на клас, могат да бъдат декларирани като
виртуални. Ключовата дума virtual може да се среща само в тялото на клас.
Например,
class Foo {public:virtual bar(); // virtual
declaration
};
int Foo::bar() { ... }
Следващата опростена
декларация на ZooAnimal декларира четири виртуални функции: debug(), locate(),
draw() и isOnDisplay().
#include <stream.h>
class
ZooAnimal
{
public:
ZooAnimal( char *whatIs = "ZooAnimal"): isA( whatIs
) {}
void isA()
{ cout << "nt" << isa << "n";
}
void setOpen( int status )
{ isOpen = status; }
virtual
isOnDisplay()
{ return isOpen; }
virtual void debug();
virtual void
draw() = 0;
protected:virtual void locate() = 0;
char *isa;
char
isOpen;
};
void ZooAnimal::debug()
{isA();cout <<
"tisOpen:"<< ((isOnDisplay()) ? "yes" :
"no") <<
"n";
}
debug(), locate(), draw() и isOnDisplay() са декларирани като
член функции на ZooAnimal защото представляват множество функции, общи за цялата
класова йерархия. Те са декларирани като виртуални, защото има детайли на
реализацията, които зависят от типа на класа и са първоначално неуточнени.
Виртуалната функция на базовия клас служи като място, където се съхраняват все
още неопределените типове класове.
Виртуална функция, дефинирана в
базовия клас на йерархията, често въобще не се вика, например locate() и draw().
Нито има някакъв смисъл в абстрактен клас като ZooAnimal. Проектантът на
класовете може да определи, че една виртуална функция не е дефинирана в
абстрактен клас като инициализира с 0 нейната декларация.
virtual void
draw() = 0;
virtual void locate() = 0;
draw() и locate() се наричат
чисто виртуални функции. Един клас с една или повече чисто виртуални функции
може да бъде използван само като базов клас за следващи извличания. Не е
правилно създаването на обекти от клас, съдържащ чисто виртуални функции.
Например, следващите две дефиниции на ZooAnimal предизвикват грешка по време на
компилация:
ZooAnimal *pz = new ZooAnimal; // error
ZooAnimal za; //
error
Само абстрактен клас, за който не се предвижда да има собствени
образци, може да декларира чисто виртуална функция.
Класът, който първи
декларира една функция като виртуална, трябва също да я декларира като чисто
виртуална функция или да осигури дефиниция.
- ако е осигурена дефиниция,
тя служи като образец по премълчаване ,ако извличаният клас не осигури свой
образец на виртуалната функция.
- ако е декларирана чисто виртуална функция,
извлеченият клас трябва или да дефинира образец на функцията, или да я декларира
отново като чисто виртуална функция. Например, Bear трябва или да осигури
дефиниции за draw() и locate(), или да ги декларира отново като чисто виртуални
функции.
Какво да правим, обаче, ако възнамеряваме да декларираме обекти
от типа клас Bear, но все още желаем да отсрочим реализацията на draw() докато
не бъдат извлечени отделни видове като Panda или Grizzly ? Не можем да
декларираме draw() като чисто виртуална функция и да продължаваме да дефинираме
обекти от класа. Ето три алтернативни решения на въпроса:
1. Дефинираме
празен образец на виртуална функция: class Bear :
public ZooAnimal {
public:void draw() {}// ...
};
2. Дефинираме образец, обръщението
към който предизвиква вътрешна грешка: void Bear::draw()
{ error( INTERNAL,
isa, "draw()" ); }
3. Дефинираме образец, който да следи за неочаквано
поведение при начертаването на родовия образ на Bear. Тоест, системата
продължава работата си, но същевременно се събира информация за изключенията по
време на изпълнение, които трябва да бъдат обработени
по-нататък.
Извлечен клас може да осигурява свой собствен образец на
виртуална функция или по премълчаване да онаследява образеца на базовия клас. На
свой ред, той може да въведе своя собствена виртуална функция. Например, Bear
предефинира debug(), locate() и draw(); той онаследява образеца на isOnDisplay
от ZooAnimal. В допълнение Bear дефинира две нови виртуални функции hibernates()
и feedingHours(). Дефиницията на Bear е опростена с цел да се подчертае
значението на виртуалните функции.
class Bear :
{ public
ZooAnimal
public:Bear( char *whatIs = "Bear" ): ZooAnimal( whatIs
),
feedTime( "2:30" ) {} // intentionally null
void draw(); // replaces
ZooAnimal::draw
void locate(); // replaces ZooAnimal::locate
virtual char
*feedingHours()
{ return feedime; }
protectted:void debug(); // replaces
ZooAnimal::debug
virtual hibernates() { return 1; }
char
*feedTime;
};
Предефинирането на една виртуална функция в извлечен
клас трябва точно да съответства на името, сигнатурата и типа на връщане на
образеца на базовия клас. Ключовата дума virtual не е нужно да бъде
специфицирана (макар, че би могла да бъде при желание на потребителя).
Дефиницията е като на обикновена член функция.
void
Bear::debug()
{isA();cout << "tfeedTime:"
<< feedingHours()
<< "n";}
void Bear::draw() {/*...code goes here*/}
void
Bear::locate() {/*...code goes here*/}
Целият виртуален механизъм
всъщност се осъществява от компилатора. Проектантът на класовете само трябва да
зададе ключовата дума virtualпри пъървоначалната дефиниция на всеки
образец.
Ако повторната декларация в извлечения клас не се съпоставя
точно, функцията не се третира като виртуална за извлечения клас. Например, ако
Bear декларира debug() по един от следните начини:
void *Bear::debug()
{...}// different return type
void Bear::debug( int ) {...}// different
signature
debug() няма да бъде виртуална за класа Bear. Например,
Bear
b;
ZooAnimal &za = b;
za.debug(); // invoke
ZooAnimal::debug()
Един извлечен след това клас от Bear, обаче, все още
може да осигурява виртуален образец на debug(), дори Bear да не може.
Например,
class Panda : public Bear {
public:
void debug(); //
virtual instance
//
...
};
Образецът на Panda е също виртуален
поради точното съпоставяне с виртуалната декларация на debug():
Panda
p;
ZooAnimal &za = p;
za.debug(); // Panda::debug()
Забележете,
че нивата на защита за две от виртуалните функции са различни за образеца на
базовия клас и образеца на извлечения клас. Образецът на locate() в ZooAnimal е
protected, докато образецът в Bear е public. Аналогично, образецът на debug() в
ZooAnimal е public, докато образецът в Bear е protected.
Какви са
всъщност нивата на защита на locate() и debug()? Например, целта ни е да напишем
такива общи функции като:
void debug( ZooAnimal& z )
{// compiler
resolves intended instance
z.debug();}
Тъй като Bear::debug() е
protected, верни ли са следните обръщения?
main()
{
// outputs :
Bear
// feedTime : 2.30
Bear ursus;
debug( ursus );}
Отговорът е
не: debug(ursus) не е вярно. Нивото на достъп на виртуалната функция се определя
от типа на класа на указателя или псевдонима, чрез който се извиква член
функцията. Всяко виртуално обръщение се третира като public, понеже debug() е
public член на ZooAnimal. Забележете, че и този образец, и следващия
предполагат, че ZooAnimal не декларира чисто виртуални
функции.
main()
{ ZooAnimal *pz = new Bear;
pz->debug(); //
invokes Bear::debug()
pz = new ZooAnimal;pz->setOpen(1); // open the
zoo
pz->debug(); // invokes ZooAnimal::debug() }
след компилация
изходът е:
Bear
feedTime: 2:30
ZooAnimal
isOpen:
yes
Извикванията на виртуалните образци на debug() чрез типа клас Bear,
обаче, могат да бъдат третирани като имащи достъп protected. Следващите
извиквания ще бъдат отбелязани като имащи нелегален достъп на protected член на
клас:
main() {
ZooAnimal *pz = new Bear;
pz->debug(); // invokes
Bear::debug()
Bear *pb = (Bear *) new ZooAnimal;
// dangerous illegal :
main has no access privilege
// to the protected members of
Bear!
pb->debug();}
Аналогично, locate() e protected член на
ZooAnimal и същевременно е public член на Bear. Виртуалните извиквания чрез
указател на Bear или псевдоним ще бъдат третирани като имащи public достъп.
Виртуалните извиквания чрез указател или псевдоним на ZooAnimal, обаче, ще бъдат
третирани като имащи protected достъп. Следващото извикване се маркира като
имащо нелегален достъп на protected член докато не член функцията locate() не се
направи friend за ZooAnimal.
void locate( ZooAnimal *pz )
{// locate()
has no access
privilege;// unless made friend to
ZooAnimal
pz->locate(); // error
}
Тези два примера са показани,
за да илюстрират, че макар че виртуалните функции се разрешават по време на
изпълнение, за тях остават в сила правилата за достъп при скриването на
информацията. Единствената аномалия е в това, че нивото на достъп на виртуална
функция е нивото, определено от типа клас, чрез който се осъществява
обръщението.
Bear осигурява свои собствени образци на три от четирите
виртуални функции, които дефинира ZooAnimal; допълнително той също дефинира две
виртуални функции. Извличане на Panda наследява шестте виртуални функции,
достъпни в рамките на базовия клас Bear.
Коя от трите виртуални функции
на ZooAnimal е предефинирана в рамките на Bear? Те остават членове, наследени от
ZooAnimal, и отново могат явно да бъдат викани. Например, debug() може да бъде
отново реализирана по следния начин:
void
Bear::debug()
{ZooAnimal::debug();
cout << "Fed time: "<<
feedTime << "n";}
Panda се извлича от три базови класа: Bear,
Endangered и Herbivore. И Endangered, и Herbivore дефинират две виртуални
функции:
class Endangered
{
public:
virtual adjustPopulation( int
);
virtual highlight( short ); //...
};
class
Herbivore
{
public:
virtual inform( ZooAnimal& );
virtual
highlight( short ); //...
};
Panda онаследява десетте виртуални
функции, дефинирани в трите базови класа. Тя може да осигурява свои собствени
образци за всяка от виртуалните си функции. В допълнение, тя може да въведе
собствени виртуални функции.
Panda онаследява две виртуални функции,
наречени highlight() една при извличането от Endangered и една при извличането
от Herbivore. Това е проблематично само ако highlight() се извлича от тип клас
Panda или ако типът клас Panda е обект на извличане. И в двата случая
използването на псевдоним на highlight() е двузначно. Например,
RedPanda
: public Panda { ... };
RedPanda pandy;
Panda &rp =
pandy;//...
rp.highlight(); // error : ambiguous
За да се избегне
евентуална двузначност, Panda дефинира собствен образец на highlight() (виж
Параграф 7.4). Следващата дефиниция на класа Panda е опростено с цел да се
наблегне на декларирането на виртуална функция при многократно
онаследяване.
class Panda : public Bear, public Endangered,public
Herbivore
{
public:
Panda( char *whatIs = "Panda: ) : Bear( whatIs )
{}
virtual onLoan();
inform( ZooAnimal& );
void draw();
int
debug();
void locate();
hibernates() { return 0;
}
protected:
highlight( short );
short cell;
short
onLoan;
};
Базовият клас знае за предефинирането на неговите виртуални
функции при следващите извличания на класа. Виканият виртуален образец се
определя чрез действителния тип на класа на указател към базовия клас или
псевдоним. Базовият клас, обаче не знае за въведените в следващите извличания
виртуални функции. Например, не е възможно да се извика hibernate(), въведена от
Bear, чрез псевдоним или указател към ZooAnimal. Не е вярно, например,
следното:
int hibernate( ZooAnimal &za )
{
// error : hibernate
not a member of ZooAnimal
}
Bear е “базов клас” за виртуалната функция
hibernate(). Само класовете от неговото ниво в йерархията на онаследяването имат
достъп до него. Ако hibernate() е общо действие за широко множество от класове в
йерархията на онаследяването, нейната дефиниция не принадлежи на Bear.
hibernate() трябва да бъде преместена по-нагоре в йерархията (в този случай се
превръща в член на ZooAnimal) или нивата на йерархията трябва да се
препроектират. И в двата случая hibernate() трябва да бъде достъпна до всички
класове в йерархията, чието общо действие описва.
Таблица 8.1 показва
активните виртуални функции в Panda. Втората колона представлява списък на
класовете, в които са дефинирани активните функции; третата колона - класът, в
който виртуалната функция е първоначално дефинирана.
Виртуална функция
Активна функция Първа дефиниция
isOnDisplay() ZooAnimal locate()
Panda
ZooAnimal draw()
Panda ZooAnimal debug()
Panda ZooAnimal
feedingHours()
Bear Bear hibernates()
Bear
Bear
adjustPopulation(int)
Endangered
Endangered highlight(short)
Panda
Endangered/ Herbivore
Panda
inform(ZooAnimal&)
Herbivore
onLoan()
Panda Panda
Таблица 8.1 Виртуални функции на
Panda
Трудно е да се направи добро проектиране на йерархия на
онаследяването. Проектантът трябва да бъде готов да премине през много
повторения. Проектирането на йерархии е област на активни изследвания и все още
няма съгласие относно това какви правила трябва да се следват при
проектирането.
Упражнение 8-1. Сред виртуалните функции, въведени в
дефиницията на Bear, има една, която не е на своето място. Коя е тя? Къде трябва
да бъде поместена? Защо?
Упражнение 8-2. Параграф 7.4 представя
първоначалната дефиниция на ZooAnimal, Bear и Panda. Тези дефиниции са
използвани за илюстрация на механизма на извличането и не е необходимо да бъдат
пример за най-добрия проект на йерархията ZooAnimal. Предефинирайте дефинициите
на класовете, включително множеството от виртуалните функции и образците на X(
const X& ) и operator = ( const X& ).
Упражнение 8-3. Проектирайте
отново член функцията debug() на йерархията Shapes, описана в Параграф 7.4 да
бъде виртуална член функция.
Упражнение 8-4. Реализирайте отново не член
функцията debug(), описана в Параграф 7.7 да осъществява виртуална реализация на
член функцията debug() на йерархията Shapes.
Упражнение 8-5. Реализирайте
виртуалната функция draw() за йерархията Shapes.
Упражнение 8-6. Реализирайте
виртуалната функция reSize() за йерархията Shapes.
Виртуални
деструктори
Свързаният списък от елементи на ZooAnimal, подаден на
finalCollage(), не е нужен повече, след като завърши изпълнението на функцията.
Обикновено се добавя for-loop от следния вид в края на finalCollage() за
освобождаването на памет:
for ( ZooAnimal *p = pz->next; p;pz = p, p =
p->next )
delete pz;
За нещастие тази стратегия не върши
работа. Явното изтриване на pz предизвиква прилагането на деструктора на
ZooAnimal към обект, сочен от pz. Обаче обектът може да не е ZooAnimal, а
някакъв след това извлечен тип клас като Bear, трябва да бъде извикан
дестукторът от действителния тип на класа, сочен от указателя. Но всяко явно
извикване отново връща към неприятностите, които възникват при използването на
детайли по приложението на йерархията на извличането.
for ( ZooAnimal *p
= pz->next; p;pz = p, p = p->next )
switch( pz->isA() )
{
case BEAR:// direct invocation of
destructor
((Bear*)pz)->Bear::~Bear();break;
case PANDA:// indirect
invocation through delete
delete (Panda *) pz;break;
//... more cаses go
here
}
Определянето на деструкторите в йерархията на извличането като
виртуални осигурява извикването на нужния деструктор при прилагането на delete
към указател към един клас. Следователно, едно ръководно правило е: деструкторът
на един абстрактен клас винаги трябва да се определя като
виртуален.
Упражнение 8-7. Дефинирайте деструкторите на йерархията Shapes
като виртуални.
Извикване на виртуални функции
Виртуална функция
се вика чрез указател към даден тип клас или негов псевдоним. Множеството от
потенциални виртуални функции, които могат да бъдат да извиквани при всяко
обръщение, се състои от:
- образецът, дефиниран от викащия тип клас
-
тези образци, които са предефинирани от следващи извличания.
Образецът на
виртуалната функция, който се изпълнява при всяко обръщение, се определя от
действителния тип на класа, сочен от указателя или
псевдонима.
Най-всобхватния тип на клас, чрез който се извиква виртуална
функция е абстрактният базов клас на йерархията на извличането в нашия пример,
това е указател или псевдоним от тип ZooAnimal. ZooAnimal има достъп до цялата
верига на онаследяването. Това е целта на дефинирането на абстрактен
суперклас.
За илюстрация на извикването на виртуални функции нека да
направим едно опростено множество от дефиниции на класове. ZooAnimal ще дефинира
две виртуални функции - print() и isA() - и образец на виртуален
деструктор.
#include <sstrea.h>
enum ZooLocs { ZOOANIMAL, BEAR,
PANDA };
class ZooAnimal
{
public:ZooAnimal( char *s = "ZooAnimal"
);
virtual ~ZooAnimal() { delete name; }
void link( ZooAnimal*
);
virtual void print( ostream& );
virtual void isA( ostream&
);
protected:
char *name;
ZooAnimal *next;
};
#include
<string.h>
ZooAnimal::ZooAnimal( char *s ) : next( 0 )
{
name =
new char[ strlen(s) + 1 ];
strcpy( name, s );
}
Идеята да се
построи смесен лист от извличания от ZooAnimal, свързани с член next.
Действителният тип клас на листа не е нужно да бъде известен на програмиста;
механизмът на виртуалните функции ще определи типа на клас за всеки
елемент.
Функцията link() на ZooAnimal приема аргумент от тип ZooAnimal*
и го присвоява на next. Това е реализирано по следния начин:
void
ZooAnimal::link( ZooAnimal *za )
{
za->next = next;
next =
za;}
isA() и print() са реализирани като виртуални функции. Всяко
следващо извличане ще дефинира свой образец на тези две функции. isA() съобщава
своя тип клас; print() усъвършенства представянето на типа клас. Всяка една от
двете функции приема като аргумент псевдоним на ostream. Ето
реализацията:
void ZooAnimal::isA( ostream& os )
{
os <<
"ZooAnimal name : "<< name << "n";}
void ZooAnimal::print(
osream& os )
{
isA( os ); // virtual invocation
}
Една от
целите на нашия проект е поддържането вход-изход за всеки тип клас, който е член
на йерархията на онаследяване ZooAnimal. За да се постигне това, трябва да
презаредим оператора за изход да възприема аргумент псевдоним на ZooAnimal. Тази
функция е реализирана по следния начин:
#include
<stream.h>
ostream&
operator << ( ostream& os,
ZooAnimal& za )
{za.print( os );return os;}
Сега програмистът може
да пренасочи всеки член на йерархията на онаследяване ZooAnimal към оператор за
изход и има извикана коректната виртуална функция print(). Ще видим един пример
за това след като дефинираме типовете класове Bear и Panda. Забележете, че
операторът функция не е направен friend за ZooAnimal. Няма необходимост той да
бъде обявен като friend тъй като неговият достъп е ограничен с публичния
интерфейс на ZooAnimal. Дефиницията на класа Bear изглежда по следния
начин:
class Bear : public ZooAnimal
{
public:
Bear( char *s =
"Bear", ZooLocs loc = BEAR,char *sci = "Ursidae" );~
Bear() { delete sciName;
}
void print( ostream& );
void isA( ostream&
);
protected:
char *sciName; // scientific nameZooLocs
zooArea;
};
#include <string.h>
Bear::Bear( char *s, ZooLocs
loc, char *sci ) : ZooAnimal( s ), zooArea( loc )
{
sciName = new char[
strlen(sci) + 1 ]; strcpy( sciName, sci );
}
Bear въвежда два
допълнителни члена данни:* sciName, научното наименование на животното.*
zooArea, мястото в зоопарка, обитавано от животното.
Виртуалните образци
на isA() и print() на Bear дават отражение върху представянето му. Те са
реализирани по следния начин:
void Bear::isA( ostream& os
)
{
ZooAnimal::isA( os ); // static invocation
os <<
"tscientific name:t";
os << sciName << "n";
}
static
char *locTable[] =
{
"The entire animal display area", //
ZOOANIMAL
"NorthWest : B1 : area Brown", // BEAR
"NorthWest : B1.P : area
BrownSpots" // PANDA
// ... and so on
};
void Bear::print(
ostream& os )
{ ZooAnimal::print( os ); // static invocation
os
<< "tZoo Area Location:nt";
os << locTable[ zooArea ] <<
"n";
}
Има три случая, в които се прави извикване на виртуална
функция статично по време на компилиране:
1. Когато виртуалната функция
се вика чрез обект от типа клас. В следващия откъс от програма, например,
функцията isA() се вика чрез обекта za от тип ZooAnimal и това става статично. А
викането на isA() чрез указател pz към обект от типа ZooAnimal се разглежда като
виртуално обръщение.
#include "ZooAnimal.h"
main()
{
ZooAnimal
za;
ZooANimal *pz;
// ...
za.isA( cout ); // nonvirtual
invocation
(*pz).isA( cout ); // virtual invocation}
2. Когато
виртуалната функция се вика явно чрез указател или псевдоним с използването на
оператора за класов обхват. Например,
#include
<stream.h>
#include "Bear.h"
#include "ZooAnimal.h"
main()
{
Bear yogi ( "cartoon Bear", BEAR,"ursus cartoons" );
ZooAnimal circus(
"circusZooAnimal" );
ZooAnimal *pz;
pz = &circus;cout <<
"virtual : ZooAnimal::print()n";
pz->print( cout );
pz =
&yogi;cout << "nvirtual : Bear::print()n";
pz->print( cout
);
cout << "nnonvirtual : ZooAnimal::print()n";
cout <<
"note : isA() is invoked virtuallyn";
pz->ZooAnimal::print( cout
);}
При компилация и изпълнение се получава следното:
virtual :
ZooAnimal::print()
ZooAnimal name : circus
ZooAnimal virtual :
Bear::print()
ZooAnimal name : cartoon Bear
scientific name : ursus
cartoonus
Zoo Area Location:NorthWest : B1 : area Brown
nonvirtual :
ZooAnimal::print()
note: isA() is invoked virtually
ZooAnimal name:
cartoon Bear
scientific name: ursus cartoonus
3. Когато виртуалната
функция се вика в рамките на конструктора или деструктора на базовия клас. И в
двата случая си вика образецът на виртуалната функция в базовия клас, тъй като
обектът от извлечения клас или не е създаден още, или вече е
изтрит.
Panda въвежда два допълнителни члена данни: indName, името на
отделното животно и cell, клетката, в която живее то. Ето дефиницията на
Panda:
#include <stream.h>
class Panda :
public Bear
{
public:
Panda( char *nm, int room, char *s = "Panda",char *sci
=
"Ailuropoda Melaoleuca", ZooLocs loc = PANDA );
~Panda() { delete
indName; }
void print( ostream& );
void isA( ostream&
);
protected:
char *indName; // name of individual animal
int
cell;
};
#include <string.h>
Panda::Panda( char *nm, int
room, char *s,char *sci, ZooLocs
loc ): Bear( s, loc, sci ), cell( room
)
{ indName = new char [strlen(nm) + 1];
strcpy( indName, nm
);}
Виртуалните образци на isA() и print(), които Panda осигурява, влияят
върху представянето му. Те са реализирани по следния начин:
void
Panda::isA( ostream& os )
{
Bear::isA( os );
os << "twe call
our friend:t";
os << indName << "n";
}
void Panda::print(
os )
{
Bear::print( os );
os << "tRoom Location:t";
os
<< cell << "n";
}
Сега да приложим всичко това в няколко
примера. Първият ни пример показва виртуално извикване на print() чрез псевдоним
на ZooAnimal. Всеки обект от класа се подава на презаредения образец на
оператора за изход (“<<”). Всяко обръщение към
za.print( os
);
в рамките на образец на оператора за изход извиква виртуалния образец,
дефиниран чрез действителния тип клас на za.
#include
<iostream.h>
#include "ZooANimal.h"
#include "Bear.h"
#include
"Panda.h"
ZooANimal circus( "circusZooAnimal");
Bear yogi("cartoon
Bear",BEAR,"ursus cartoonsus");
Panda yinYang("Yin Yang",1001,"Giant
Panda");
main() {
cout << "Invokation by a ZooAnimal
object:n"
<< circus << "n";
cout << "nInvokation by a
Bear object:n"
<< yogi << "n";
cout << "nInvokation
by a Panda object:n"
<< yinYang << "n";
};